package server

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/YaxiongWu/remote-control-webrtc/pkg/stun"
	"github.com/go-logr/logr"
	"github.com/pion/webrtc/v3"
	"github.com/sourcegraph/jsonrpc2"
)

// Join message sent when initializing a peer connection
type Join struct {
	SID    string                    `json:"sid"`
	UID    string                    `json:"uid"`
	Offer  webrtc.SessionDescription `json:"offer"`
	Config stun.JoinConfig           `json:"config"`
}

// Negotiation message sent when renegotiating the peer connection
type Negotiation struct {
	Desc webrtc.SessionDescription `json:"desc"`
}

// Trickle message sent when renegotiating the peer connection
type Trickle struct {
	Target    int                     `json:"target"`
	Candidate webrtc.ICECandidateInit `json:"candidate"`
}

type JSONSignal struct {
	*stun.ClientLocal
	logr.Logger
}

func NewJSONSignal(c *stun.ClientLocal, l logr.Logger) *JSONSignal {
	return &JSONSignal{c, l}
}

/*
关于接入流程的问题：
两种方案，以尽量不修改grpc-json的js代码为原则：

1.视频源先发信号来新建，只登记seesion id 和 client id，用grpc来保持连接，当观看者接入时，观看者要作为发起者，发送join.offer,
  服务器转发 join.offer 给视频源，视频源回应answer

2.不做新建，按pion/ion-sfu的框架和流程，视频源标注一个config，join.offer保存在服务器端，等观看者接入时再转发。

  如果join.offer来的时候不做处理呢？只用config:VideoSource来标识，观看者接入的时候，将观看者的join.offer发给视频源呢？这样不需要修改protoc

*/
// Handle incoming RPC call events like join, answer, offer and trickle
func (c *JSONSignal) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
	replyError := func(err error) {
		_ = conn.ReplyWithError(ctx, req.ID, &jsonrpc2.Error{
			Code:    500,
			Message: fmt.Sprintf("%s", err),
		})
	}

	switch req.Method {
	case "join":
		var join Join
		err := json.Unmarshal(*req.Params, &join)
		//fmt.Printf("join:%v\r\n", join)
		stun.LogDebug.Print("json recv,join:\r\n", join)
		if err != nil {
			//p.Logger.Error(err, "connect: error parsing offer")
			replyError(err)
			break
		}

		JoinReqID := req.ID
		c.Join(join.SID, join.UID)
		/*
			JoinReqID 有什么用？
			conn.Reply，conn.Notify 有什么区别？
			conn.Reply是回复，resp
			conn.Notify是发送，发送para

		*/

		c.OnOfferGRCP2JSONReply = func(offer *webrtc.SessionDescription) {
			// if err := conn.Notify(ctx, "offer", offer); err != nil {
			// 	stun.LogDebug.Print(err, "error sending offer")
			// }

			/*
			 在join时，带了一个datachannel，并且join的发送方是要等待回复的，所以用conn.Reply,
			 这个conn.Reply是在grpc，即收到树莓派SignalRequest_Description时运行的

			 如果是树莓派发起offer,再用这个Reply，网页将收不到，需要用Notify
			*/
			stun.LogDebug.Print(offer)
			_ = conn.Reply(ctx, JoinReqID, offer)
		}

		c.OnOfferGRCP2JSONNotify = func(offer1 *webrtc.SessionDescription) {
			stun.LogDebug.Print(offer1)
			if err1 := conn.Notify(ctx, "offer", offer1); err1 != nil {
				stun.LogDebug.Print(err1, "error sending offer")
			}
		}

		c.OnIceCandidateGRCP2JSON = func(candidate *webrtc.ICECandidateInit, target int) {
			if err := conn.Notify(ctx, "trickle", Trickle{
				Candidate: *candidate,
				Target:    target,
			}); err != nil {
				stun.LogDebug.Print(err, "error sending ice candidate")
			}
			//这里要注意搞清代码含义！！
			//_ = conn.Reply(ctx, JoinReqID, candidate)
		}

		clients := c.Session().Clients()

		for client := range clients {
			if clients[client].GetID() != join.UID {
				if clients[client].OnOfferJSON2GRCP != nil {
					clients[client].OnOfferJSON2GRCP(&join.Offer)
				}
			}
		}

		// for s := range c.ProviderSessions() {
		// 	fmt.Printf("c.ProviderSessions(),session id:%v\r\n", s)
		// }
		//s, cfg := p.client.provider.GetSession(join.SID)
		//p.session = s

		// p.OnOffer = func(offer *webrtc.SessionDescription) {
		// 	if err := conn.Notify(ctx, "offer", offer); err != nil {
		// 		p.Logger.Error(err, "error sending offer")
		// 	}

		// }
		// p.OnIceCandidate = func(candidate *webrtc.ICECandidateInit, target int) {
		// 	if err := conn.Notify(ctx, "trickle", Trickle{
		// 		Candidate: *candidate,
		// 		Target:    target,
		// 	}); err != nil {
		// 		p.Logger.Error(err, "error sending ice candidate")
		// 	}
		// }

		// err = p.Join(join.SID, join.UID, join.Config)
		// if err != nil {
		// 	replyError(err)
		// 	break
		// }

		// answer, err := p.Answer(join.Offer)
		// if err != nil {
		// 	replyError(err)
		// 	break
		// }

		// _ = conn.Reply(ctx, req.ID, answer)

	case "offer":
		var negotiation Negotiation
		err := json.Unmarshal(*req.Params, &negotiation)
		if err != nil {
			//p.Logger.Error(err, "connect: error parsing offer")
			replyError(err)
			break
		}
		c.OnOfferGRCP2JSONReply = func(offer *webrtc.SessionDescription) {
			if err := conn.Notify(ctx, "offer", offer); err != nil {
				stun.LogDebug.Print(err, "error sending offer")
			}
			stun.LogDebug.Print(offer)
			//_ = conn.Reply(ctx, JoinReqID, offer)
		}
		clients := c.Session().Clients()
		for client := range clients {
			if clients[client].GetID() != c.GetID() {
				if clients[client].OnOfferJSON2GRCP != nil {
					clients[client].OnOfferJSON2GRCP(&negotiation.Desc)
				}
			}
		}

		// answer, err := p.Answer(negotiation.Desc)
		// if err != nil {
		// 	replyError(err)
		// 	break
		// }
		// _ = conn.Reply(ctx, req.ID, answer)

	case "answer":
		var negotiation Negotiation
		err := json.Unmarshal(*req.Params, &negotiation)
		if err != nil {
			//p.Logger.Error(err, "connect: error parsing answer")
			replyError(err)
			break
		}
		clients := c.Session().Clients()
		for client := range clients {
			if clients[client].GetID() != c.GetID() {
				if clients[client].OnOfferJSON2GRCP != nil {
					clients[client].OnOfferJSON2GRCP(&negotiation.Desc)
				}
			}
		}
		// err = p.SetRemoteDescription(negotiation.Desc)
		// if err != nil {
		// 	replyError(err)
		// }

	case "trickle":
		var trickle Trickle

		err := json.Unmarshal(*req.Params, &trickle)
		stun.LogDebug.Print("json rev trickle:", trickle)
		if err != nil {
			stun.LogError.Print(err, "connect: error parsing candidate")
			replyError(err)
			break
		}

		clients := c.Session().Clients()
		for client := range clients {
			if clients[client].GetID() != c.GetID() {
				if clients[client].OnIceCandidateJSON2GRCP != nil {
					clients[client].OnIceCandidateJSON2GRCP(&trickle.Candidate, trickle.Target)
				}
			}
		}

		// err = p.Trickle(trickle.Candidate, trickle.Target)
		// if err != nil {
		// 	replyError(err)
		// }
	}
}
